import { track, trigger } from "./effect.js";

/**
 * @description 创建响应式对象
 * @param {*} data 源数据
 * @param {*} isShallow 是否为浅响应式
 * @returns
 */
function createReactive(data, isShallow = false, isReadonly = false) {
  /* 拦截for循环中，与副作用函数进行关联的key */
  let ITERATE_KEY = Symbol();

  /* 调用keys方法时与副作用函数进行关联的key */
  const MAP_KEY_ITERATE_KEY = Symbol();

  /* 重写数组方法 */
  const arrayInstrumenttation = {};

  ["includes", "indexOf", "lastIndexOf"].forEach((method) => {
    /* 拷贝数组方法 */
    const originMethod = Array.prototype[method];

    /* 重写数组方法 */
    arrayInstrumenttation[method] = function (...args) {
      console.log(`执行重写的数组的${method}方法`);
      /* 先在代理对象自身查找是否存在 */
      let res = originMethod.apply(this, args);
      /* 如果代理对象自身没有找到 */
      /* 则到代理对象的原始对象上进行查找 */
      if (res === false || res === -1) {
        res = originMethod.apply(this.raw, args);
      }

      return res;
    };
  });

  /* 是否允许收集依赖（副作用函数） */
  let shouldTrack = true;

  ["push", "pop", "unshift", "shift", "splice"].forEach((method) => {
    const originMethod = Array.prototype[method];

    arrayInstrumenttation[method] = function (...args) {
      /* 在数组栈方法默认行为之前设置为false，表示禁止依赖收集 */
      shouldTrack = false;

      const res = originMethod.apply(this, args);

      /* 在数组站方法默认行为之后设置为true，表示进行依赖收集 */
      /* 避免由于栈方法内部读取设置length行为而导致栈溢出 */
      shouldTrack = true;

      return res;
    };
  });

  /* Set/Map的方法集 */
  const mutableMethods = [
    "add",
    "delete",
    "get",
    "set",
    "forEach",
    "entries",
    "values",
    "keys",
    Symbol.iterator,
  ];

  /* 重写Set/Map方法 */
  const mutableInstrumentations = {
    get(key) {
      /* 拿到原始Map对象 */
      /* 因为是代理Map对象调用的，所以this指向代理对象 */
      /* 当代理对象访问raw属性时，返回原始对象 */
      const target = this.raw;

      /* 值是否已存在Map中 */
      const hadKey = target.has(key);

      if (hadKey) {
        track(target, key);
        /* 拿到key属性值 */
        const res = target.get(key);
        /* 如果为对象包装为代理对象，否则直接返回值 */
        return typeof res === "object" ? reactive(res) : res;
      }
    },
    set(key, val) {
      /* 拿到原始Map对象 */
      /* 因为是代理Map对象调用的，所以this指向代理对象 */
      /* 当代理对象访问raw属性时，返回原始对象 */
      const target = this.raw;

      /* 获取key对应的值 */
      const oldVal = target.get(key);

      /* 值是否已存在Map中 */
      const hadKey = target.has(key);

      /* 获取原始数据如果是原始数据那么raw就不存在，表示本来就是原始数据直接使用val的值，如果存在就获取raw的值（原始对象） */
      const valRaw = val.raw || val;

      /* 设置Map的键值对 */
      const res = target.set(key, valRaw);

      /* 如果键值对不存在于Map中，触发副作用函数 */
      if (!hadKey) {
        trigger(target, { key, ITERATE_KEY }, "ADD");
      } else if (oldVal !== val && (oldVal === oldVal || val === val)) {
        /* key存在，以及新的值不等于旧的值、并且新值或旧值不等于NaN */
        trigger(target, { key, ITERATE_KEY }, "SET");
      }

      /* 返回Map对象 */
      return res;
    },
    add(val) {
      /* 拿到原始Set对象 */
      /* 因为是代理Set对象调用的，所以this指向代理对象 */
      /* 当代理对象访问raw属性时，返回原始对象 */
      const target = this.raw;

      /* 值是否已存在Set中 */
      const hadVal = target.has(val);

      /* 原始set对象调用add方法添加值 */
      const res = target.add(val);

      /* 只有值不存在于set中才触发副作用函数 */
      if (!hadVal) trigger(target, { ITERATE_KEY }, "ADD");

      /* 返回添加值后的set对象 */
      return res;
    },
    delete(key) {
      /* 拿到原始Set/Map对象 */
      /* 因为是代理Set/Map对象调用的，所以this指向代理对象 */
      /* 当代理对象访问raw属性时，返回原始对象 */
      const target = this.raw;

      /* 值是否已存在Set/Map中 */
      const hadKey = target.has(key);

      /* 原始Set/Map对象调用add方法添加值 */
      const res = target.delete(key);

      /* 只有值存在于Set/Map中才触发副作用函数 */
      if (hadKey) trigger(target, { ITERATE_KEY }, "ADD");

      /* 返回添加值后的Set/Map对象 */
      return res;
    },

    forEach(callback, thisArg) {
      /* 拿到原始Set/Map对象 */
      /* 因为是代理Set/Map对象调用的，所以this指向代理对象 */
      /* 当代理对象访问raw属性时，返回原始对象 */
      const target = this.raw;

      /* 将可代理的原始对象转换为响应式数据 */
      const wrap = (val) => (typeof val === "object" ? reactive(val) : val);

      /* ITERATE_KEY与副作用函数建立响应关系 */
      track(target, ITERATE_KEY);

      /* 调用原始Map/Set对象的forEach方法遍历 */
      target.forEach((v, k, target) => {
        /* 通过 .call() 方法将回调函数的this指向传进来的thisArg */
        callback.call(thisArg, wrap(v), wrap(k), target);
      });
    },
    [Symbol.iterator]: createIterateionMethod(),
    entries: createIterateionMethod(),
    values: createIterateionMethod(false, "values"),
    keys: createIterateionMethod(false, "keys"),
  };

  /**
   * @description 重写迭代函数
   * @param {*} isKeyPair 是否是键值对
   * @returns
   */
  function createIterateionMethod(isKeyPair = true, type) {
    return function () {
      /* 拿到原始Set/Map对象 */
      /* 因为是代理Set/Map对象调用的，所以this指向代理对象 */
      /* 当代理对象访问raw属性时，返回原始对象 */
      const target = this.raw;

      /* 获取迭代器函数 */
      let itr = isKeyPair ? target[Symbol.iterator]() : target[type]();

      /* 将可代理的对象进行代理 */
      const wrap = (val) => (typeof val === "object" ? reactive(val) : val);

      /* 收集副作用函数 */
      if (type === "keys") {
        track(target, MAP_KEY_ITERATE_KEY);
      } else {
        track(target, ITERATE_KEY);
      }

      return {
        next() {
          /* 调用原始对象迭代器的next方法获取value和done */
          const { value, done } = itr.next();

          return {
            value: value
              ? isKeyPair
                ? [wrap(value[0]), wrap(value[1])]
                : wrap(value)
              : value, // 如果value不是undefined则对其进行包装
            done,
          };
        },
        /* 返回迭代器函数防止代理对象.entries时报错：xx.entries 的返回值不是一个可迭代对象 */
        [Symbol.iterator]() {
          return this;
        },
      };
    };
  }

  return new Proxy(data, {
    get(target, key, receiver) {
      if (key === "raw") {
        return target;
      }

      /* 如果target是数组，并且读取的key是存在于 重写的数组方法对象中 */
      /* 那么返回重写的数组方法 */
      if (Array.isArray(target) && arrayInstrumenttation[key]) {
        return Reflect.get(arrayInstrumenttation, key, receiver);
      }

      /* 如果读取的是size属性 */
      /* 只当receiver也就是this指向原始对象 */
      if (key === "size") {
        track(target, ITERATE_KEY);
        return Reflect.get(target, key, target);
      }

      /* 如果代理Set/Map对象调用了add...方法，返回自定义的方法 */
      if (mutableMethods.includes(key)) {
        return mutableInstrumentations[key];
      }

      const res = Reflect.get(target, key, receiver);

      /* 如果是浅层响应，直接返回值 */
      if (isShallow) {
        return res;
      }

      /* 深响应：被访问的属性是对象 */
      if (typeof res === "object" && res !== null) {
        return isReadonly ? readonly(res) : reactive(res);
      }
      /* 非只读的属性，并且key不为symbol才进行依赖（副作用函数）收集 */
      if (!isReadonly && typeof key !== "symbol") {
        track(target, key, shouldTrack);
      }

      return res;
    },
    set(target, key, value, receiver) {
      console.log("设置了obj的属性");
      console.log(key);
      /* 只读 */
      if (isReadonly) {
        console.warn(`属性${key}是只读的`);
        return true;
      }

      const oldValue = target[key];

      /* 如果代理的是数组，则检测被设置的索引值是否小于数组长度 */
      /* 如果是则视为修改，否则是新增 */
      /* 如果代理的是对象，则检测被设置的属性存不存在于代理对象 */
      /* 如果存在则视为修改，否则为新增 */
      const type = Array.isArray(target)
        ? Number(key) < target.length
          ? "SET"
          : "ADD"
        : target.hasOwnProperty(key)
        ? "SET"
        : "ADD";

      const res = Reflect.set(target, key, value, receiver);
      /* 判断当前触发set函数的是否是对象本身，屏蔽由原型引起的更新 */
      if (target === receiver.raw) {
        /* 当新值不等于旧值并且新值或者旧值不为NaN */
        if (oldValue !== value && (oldValue === oldValue || value === value)) {
          trigger(
            target,
            { key, ITERATE_KEY, MAP_KEY_ITERATE_KEY },
            type,
            value
          );
        }
      }

      return res;
    },
    has(target, key) {
      track(target, key);
      return Reflect.has(target, key);
    },
    ownKeys(target) {
      track(target, Array.isArray(target) ? "length" : ITERATE_KEY);
      return Reflect.ownKeys(target);
    },
    deleteProperty(target, key) {
      /* 只读 */
      if (isReadonly) {
        console.warn(`属性${key}是只读的`);
        return true;
      }

      const hadKey = target.hasOwnProperty(key);
      const res = Reflect.deleteProperty(target, key);
      if (hadKey && res) {
        trigger(target, { key, ITERATE_KEY }, "DELETE");
      }

      return res;
    },
  });
}

/* 存储原始对象到代理对象的映射 */
const reactiveMap = new Map();

/**
 * @description 创建深响应式对象
 * @param {Object} data 源数据
 */
export function reactive(obj) {
  /* 从映射表中获取原始对象的代理对象 */
  const existionProxy = reactiveMap.get(obj);
  /* 已存在则直接返回原始对象的代理对象 */
  if (existionProxy) {
    return existionProxy;
  }
  /* 否则创建新的代理对象 */
  const proxy = createReactive(obj);
  /* 创建原始对象到代理对象的映射关系，从而避免重复创建 */
  reactiveMap.set(obj, proxy);

  return proxy;
}

/**
 * @description 创建浅响应式对象
 * @param {*} obj 源数据
 */
export function shallowReactive(obj) {
  return createReactive(obj, true);
}

/**
 * @description 创建深只读响应式对象
 * @param {*} obj 源数据
 * @returns
 */
export function readonly(obj) {
  return createReactive(obj, false, true);
}

/**
 * @description 创建浅只读响应式对象
 * @param {*} obj 源数据
 * @returns
 */
export function shallowReadonly(obj) {
  return createReactive(obj, true, true);
}
